﻿using System;
using System.Configuration;
using System.Configuration.Internal;
using System.IO;
using System.Linq;
using System.Windows.Markup;
using System.Xml;
using System.Xml.Linq;

namespace Synergy.Configuration
{
	/// <summary>
	/// Represents a configuration section that generates an object from XAML, instead of requiring complex configuration section handlers.
	/// </summary>
	public class XamlConfigurationSection : ConfigurationSection
	{
		#region | Fields |

		private const string FileAttribute = "file";
		private object _value;

		#endregion


		#region | Properties |

		/// <summary>
		/// Gets or sets the file to pull the XAML from.
		/// </summary>
		/// <remarks>
		/// If specified, this property overrides XAML specified inside this config section.
		/// </remarks>
		/// <value>The file to pull XAML from.</value>
		[ConfigurationProperty(FileAttribute, IsRequired = false)]
		public string File
		{
			get { return (string)this[FileAttribute]; }
			set { this[FileAttribute] = value; }
		}

		#endregion


		#region | Methods [Public] |

		/// <summary>
		/// Gets the value as type <typeparamref name="T"/>.
		/// </summary>
		/// <typeparam name="T">The expected type of the XAML config.</typeparam>
		/// <returns>An object representing the XAML content in the configuration section.</returns>
		public T GetValue<T>() where T : class
		{
			return this._value as T;
		}


		/// <summary>
		/// Loads the configuration section with the specified <paramref name="sectionName"/>.
		/// </summary>
		/// <typeparam name="T">The expected object to retrieve from the XAML.</typeparam>
		/// <param name="sectionName">Name of the section.</param>
		/// <returns>An object representing the XAML content in the configuration section.</returns>
		public static T Load<T>(string sectionName) where T : class
		{
			var config = ConfigurationManager.GetSection(sectionName) as XamlConfigurationSection;
			return (config == null) ? null : config.GetValue<T>();
		}

		#endregion


		#region | Methods [Protected] |

		/// <summary>
		/// Reads XML from the configuration file.
		/// </summary>
		/// <param name="reader">The <see cref="XmlReader"/> that reads from the configuration file.</param>
		/// <param name="serializeCollectionKey">true to serialize only the collection key properties; otherwise, false.</param>
		/// <exception cref="ConfigurationErrorsException">The element to read is locked. - or - An attribute of the current node is not recognized. - or - The lock status of the current node cannot be determined.</exception>
		protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
		{
			// load config
			var doc = XDocument.Load(reader);
			if (doc.Root == null)
			{
				return;
			}

			// save file
			File = (string)doc.Root.Attribute(FileAttribute);

			// if no file, load from inner xml; otherwise, load from file
			if (String.IsNullOrEmpty(File))
			{
				// load value from inner xml
				var xaml = doc.Root.DescendantNodes().Where(n => n.NodeType == XmlNodeType.Element).FirstOrDefault().ToString();
				this._value = XamlReader.Parse(xaml);
			}
			else
			{
				var info = (reader as IConfigErrorInfo);
				var configFile = (info == null || String.IsNullOrEmpty(info.Filename)) ? null : Path.GetDirectoryName(info.Filename);
				var fullPath = String.IsNullOrEmpty(configFile) ? File : Path.Combine(configFile, File);
				fullPath = System.IO.File.Exists(fullPath) ? fullPath : ((fullPath != File && System.IO.File.Exists(File)) ? File : null);
				if (!String.IsNullOrEmpty(fullPath))
				{
					this._value = XamlReader.Load(System.IO.File.OpenRead(fullPath));
				}
			}
		}


		/// <summary>
		/// Writes the contents of this configuration element to the configuration file when implemented in a derived class.
		/// </summary>
		/// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> that writes to the configuration file.</param>
		/// <param name="serializeCollectionKey">true to serialize only the collection key properties; otherwise, false.</param>
		/// <returns><c>true</c> if any data was actually serialized; otherwise, <c>false</c>.</returns>
		/// <exception cref="ConfigurationErrorsException">The current attribute is locked at a higher configuration level.</exception>
		protected override bool SerializeElement(XmlWriter writer, bool serializeCollectionKey)
		{
			// TODO: Needs additional testing

			XamlWriter.Save(this._value, writer);
			return base.SerializeElement(writer, serializeCollectionKey);
		}

		#endregion
	}
}